import { ComponentDatatype } from '../../Source/Cesium.js';
import { PrimitiveType } from '../../Source/Cesium.js';
import { Buffer } from '../../Source/Cesium.js';
import { BufferUsage } from '../../Source/Cesium.js';
import { DrawCommand } from '../../Source/Cesium.js';
import { ShaderProgram } from '../../Source/Cesium.js';
import { VertexArray } from '../../Source/Cesium.js';
import createContext from '../createContext.js';

describe('Renderer/VertexArray', function() {

    var context;

    beforeAll(function() {
        context = createContext();
    });

    afterAll(function() {
        context.destroyForSpecs();
    });

    it('binds', function() {
        var positionBuffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            index : 0,
            enabled : true,
            vertexBuffer : positionBuffer,
            componentsPerAttribute : 3,
            componentDatatype : ComponentDatatype.FLOAT,
            normalize : false,
            offsetInBytes : 0,
            strideInBytes : 0,
            instanceDivisor : 0
        // tightly packed
        }];

        var va = new VertexArray({
            context : context,
            attributes : attributes
        });
        va._bind();
        va._unBind();
        va = va.destroy();
    });

    it('binds with default values', function() {
        var positionBuffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : positionBuffer,
            componentsPerAttribute : 3
        }];

        var va = new VertexArray({
            context : context,
            attributes : attributes
        });

        expect(va.numberOfAttributes).toEqual(1);
        expect(va.getAttribute(0).index).toEqual(0);
        expect(va.getAttribute(0).enabled).toEqual(true);
        expect(va.getAttribute(0).vertexBuffer).toEqual(positionBuffer);
        expect(va.getAttribute(0).componentsPerAttribute).toEqual(3);
        expect(va.getAttribute(0).componentDatatype).toEqual(ComponentDatatype.FLOAT);
        expect(va.getAttribute(0).normalize).toEqual(false);
        expect(va.getAttribute(0).offsetInBytes).toEqual(0);
        expect(va.getAttribute(0).strideInBytes).toEqual(0);
        expect(va.getAttribute(0).instanceDivisor).toEqual(0);

        va._bind();
        va._unBind();
        va = va.destroy();
    });

    it('binds with multiple buffers', function() {
        var attributeSize = 3 * Float32Array.BYTES_PER_ELEMENT;
        var positionBuffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : attributeSize,
            usage : BufferUsage.STATIC_DRAW
        });
        var normalBuffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : attributeSize,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            index : 0,
            vertexBuffer : positionBuffer,
            componentsPerAttribute : 3,
            componentDatatype : ComponentDatatype.FLOAT
        }, {
            index : 1,
            vertexBuffer : normalBuffer,
            componentsPerAttribute : 3,
            componentDatatype : ComponentDatatype.FLOAT
        }];

        var va = new VertexArray({
            context : context,
            attributes : attributes
        });

        expect(va.numberOfAttributes).toEqual(2);
        va._bind();
        va._unBind();
        va = va.destroy();
    });

    it('binds with interleaved buffer', function() {
        var attributeSize = 3 * Float32Array.BYTES_PER_ELEMENT;
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : attributeSize,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : buffer,
            componentsPerAttribute : 3,
            componentDatatype : ComponentDatatype.FLOAT,
            offsetInBytes : 0,
            strideInBytes : 2 * attributeSize
        }, {
            vertexBuffer : buffer,
            componentsPerAttribute : 3,
            componentDatatype : ComponentDatatype.FLOAT,
            normalize : true,
            offsetInBytes : attributeSize,
            strideInBytes : 2 * attributeSize
        }];

        var va = new VertexArray({
            context : context,
            attributes : attributes
        });

        expect(va.numberOfAttributes).toEqual(2);
        va._bind();
        va._unBind();
        va = va.destroy();
    });

    it('adds attributes', function() {
        var positionBuffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var va = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : positionBuffer,
                componentsPerAttribute : 3
            }]
        });

        expect(va.numberOfAttributes).toEqual(1);
        expect(va.getAttribute(0).index).toEqual(0);
        expect(va.getAttribute(0).enabled).toEqual(true);
        expect(va.getAttribute(0).vertexBuffer).toEqual(positionBuffer);
        expect(va.getAttribute(0).componentsPerAttribute).toEqual(3);
        expect(va.getAttribute(0).componentDatatype).toEqual(ComponentDatatype.FLOAT);
        expect(va.getAttribute(0).normalize).toEqual(false);
        expect(va.getAttribute(0).offsetInBytes).toEqual(0);
        expect(va.getAttribute(0).strideInBytes).toEqual(0);
        expect(va.getAttribute(0).instanceDivisor).toEqual(0);

        va = va.destroy();
    });

    it('modifies attributes', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 6,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : buffer,
            componentsPerAttribute : 3
        }];

        var va = new VertexArray({
            context : context,
            attributes : attributes
        });

        expect(va.numberOfAttributes).toEqual(1);
        expect(va.getAttribute(0).enabled).toEqual(true);

        va.getAttribute(0).enabled = false;
        expect(va.getAttribute(0).enabled).toEqual(false);

        va._bind();
        va._unBind();
        va = va.destroy();
    });

    // The following specs test draw calls that pull from a constant attribute.
    // Due to what I believe is a range checking bug in Firefox (Section 6.4 of
    // the WebGL spec), an attribute backed by a buffer must also be bound,
    // otherwise drawArrays unjustly reports an INVALID_OPERATION, hence the
    // firefoxWorkaround attribute below.  In practice, we will always have
    // an attribute backed by a buffer anyway.

    it('renders with a one-component constant value', function() {
        var vs =
            'attribute float firefoxWorkaround;' +
            'attribute float attr;' +
            'varying vec4 v_color;' +
            'void main() { ' +
            '  v_color = vec4(attr == 0.5) + vec4(firefoxWorkaround);' +
            '  gl_PointSize = 1.0;' +
            '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' +
            '}';
        var fs =
            'varying vec4 v_color;' +
            'void main() { gl_FragColor = v_color; }';
        var sp = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : {
                firefoxWorkaround : 0,
                attr : 1
            }
        });

        var va = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : Buffer.createVertexBuffer({
                    context : context,
                    sizeInBytes : Float32Array.BYTES_PER_ELEMENT,
                    usage : BufferUsage.STATIC_DRAW
                }),
                componentsPerAttribute : 1
            }, {
                value : [0.5]
            }]
        });

        var command = new DrawCommand({
            primitiveType : PrimitiveType.POINTS,
            shaderProgram : sp,
            vertexArray : va,
            count : 1
        });
        command.execute(context);

        expect(context).toReadPixels([255, 255, 255, 255]);

        sp = sp.destroy();
        va = va.destroy();
    });

    it('renders with a two-component constant value', function() {
        var vs =
            'attribute float firefoxWorkaround;' +
            'attribute vec2 attr;' +
            'varying vec4 v_color;' +
            'void main() { ' +
            '  v_color = vec4(attr == vec2(0.25, 0.75)) + vec4(firefoxWorkaround);' +
            '  gl_PointSize = 1.0;' +
            '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' +
            '}';
        var fs =
            'varying vec4 v_color;' +
            'void main() { gl_FragColor = v_color; }';
        var sp = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : {
                firefoxWorkaround : 0,
                attr : 1
            }
        });

        var va = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : Buffer.createVertexBuffer({
                    context : context,
                    sizeInBytes : Float32Array.BYTES_PER_ELEMENT,
                    usage : BufferUsage.STATIC_DRAW
                }),
                componentsPerAttribute : 1
            }, {
                value : [0.25, 0.75]
            }]
        });

        var command = new DrawCommand({
            primitiveType : PrimitiveType.POINTS,
            shaderProgram : sp,
            vertexArray : va,
            count : 1
        });
        command.execute(context);

        expect(context).toReadPixels([255, 255, 255, 255]);

        sp = sp.destroy();
        va = va.destroy();
    });

    it('renders with a three-component constant value', function() {
        var vs =
            'attribute float firefoxWorkaround;' +
            'attribute vec3 attr;' +
            'varying vec4 v_color;' +
            'void main() { ' +
            '  v_color = vec4(attr == vec3(0.25, 0.5, 0.75)) + vec4(firefoxWorkaround);' +
            '  gl_PointSize = 1.0;' +
            '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' +
            '}';
        var fs =
            'varying vec4 v_color;' +
            'void main() { gl_FragColor = v_color; }';
        var sp = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : {
                firefoxWorkaround : 0,
                attr : 1
            }
        });

        var va = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : Buffer.createVertexBuffer({
                    context : context,
                    sizeInBytes : Float32Array.BYTES_PER_ELEMENT,
                    usage : BufferUsage.STATIC_DRAW
                }),
                componentsPerAttribute : 1
            }, {
                value : [0.25, 0.5, 0.75]
            }]
        });

        var command = new DrawCommand({
            primitiveType : PrimitiveType.POINTS,
            shaderProgram : sp,
            vertexArray : va,
            count : 1
        });
        command.execute(context);

        expect(context).toReadPixels([255, 255, 255, 255]);

        sp = sp.destroy();
        va = va.destroy();
    });

    it('renders with a four-component constant value', function() {
        var vs =
            'attribute float firefoxWorkaround;' +
            'attribute vec4 attr;' +
            'varying vec4 v_color;' +
            'void main() { ' +
            '  v_color = vec4(attr == vec4(0.2, 0.4, 0.6, 0.8)) + vec4(firefoxWorkaround);' +
            '  gl_PointSize = 1.0;' +
            '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' +
            '}';
        var fs =
            'varying vec4 v_color;' +
            'void main() { gl_FragColor = v_color; }';
        var sp = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : {
                firefoxWorkaround : 0,
                attr : 1
            }
        });

        var va = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : Buffer.createVertexBuffer({
                    context : context,
                    sizeInBytes : Float32Array.BYTES_PER_ELEMENT,
                    usage : BufferUsage.STATIC_DRAW
                }),
                componentsPerAttribute : 1
            }, {
                value : [0.2, 0.4, 0.6, 0.8]
            }]
        });

        var command = new DrawCommand({
            primitiveType : PrimitiveType.POINTS,
            shaderProgram : sp,
            vertexArray : va,
            count : 1
        });
        command.execute(context);

        expect(context).toReadPixels([255, 255, 255, 255]);

        sp = sp.destroy();
        va = va.destroy();
    });

    it('renders two vertex arrays with constant values', function() {
        var vs =
            'attribute float firefoxWorkaround;' +
            'attribute vec4 attr;' +
            'varying vec4 v_color;' +
            'void main() { ' +
            '  v_color = attr + vec4(firefoxWorkaround);' +
            '  gl_PointSize = 1.0;' +
            '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' +
            '}';

        var fs =
            'varying vec4 v_color;' +
            'void main() { gl_FragColor = v_color; }';

        var sp = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : {
                firefoxWorkaround : 0,
                attr : 1
            }
        });

        var vertexBuffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : Float32Array.BYTES_PER_ELEMENT,
            usage : BufferUsage.STATIC_DRAW
        });

        var vaRed = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : vertexBuffer,
                componentsPerAttribute : 1
            }, {
                value : [1, 0, 0, 1]
            }]
        });

        var vaGreen = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : vertexBuffer,
                componentsPerAttribute : 1
            }, {
                value : [0, 1, 0, 1]
            }]
        });

        var commandRed = new DrawCommand({
            primitiveType : PrimitiveType.POINTS,
            shaderProgram : sp,
            vertexArray : vaRed,
            count : 1
        });

        var commandGreen = new DrawCommand({
            primitiveType : PrimitiveType.POINTS,
            shaderProgram : sp,
            vertexArray : vaGreen,
            count : 1
        });

        commandRed.execute(context);
        expect(context).toReadPixels([255, 0, 0, 255]);

        commandGreen.execute(context);
        expect(context).toReadPixels([0, 255, 0, 255]);

        sp = sp.destroy();
        vaRed = vaRed.destroy();
        vaGreen = vaGreen.destroy();
    });

    it('destroys', function() {
        var va = new VertexArray({
            context : context,
            attributes : [{
                vertexBuffer : Buffer.createVertexBuffer({
                    context : context,
                    sizeInBytes : new Float32Array([0, 0, 0, 1]).byteLength,
                    usage : BufferUsage.STATIC_DRAW
                }),
                componentsPerAttribute : 4
            }]
        });

        expect(va.isDestroyed()).toEqual(false);
        va.destroy();
        expect(va.isDestroyed()).toEqual(true);
    });

    it('fails to create (missing vertexBuffer and value)', function() {
        var attributes = [{
            componentsPerAttribute : 3
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (provides both vertexBuffer and value)', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : buffer,
            value : [1, 2, 3],
            componentsPerAttribute : 3
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create with duplicate indices', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 1,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            index : 1,
            vertexBuffer : buffer,
            componentsPerAttribute : 1
        }, {
            index : 1,
            vertexBuffer : buffer,
            componentsPerAttribute : 1
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (componentsPerAttribute missing)', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : buffer
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (componentsPerAttribute < 1)', function() {
        var attributes = [{
            componentsPerAttribute : 0
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (componentsPerAttribute > 4)', function() {
        var attributes = [{
            componentsPerAttribute : 5
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (value.length < 1)', function() {
        var attributes = [{
            value : []
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (value.length > 4)', function() {
        var attributes = [{
            value : [1.0, 2.0, 3.0, 4.0, 5.0]
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (componentDatatype)', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : buffer,
            componentsPerAttribute : 3,
            componentDatatype : 'invalid component datatype'
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to create (strideInBytes)', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : buffer,
            componentsPerAttribute : 3,
            strideInBytes : 256
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('fails to get attribute', function() {
        var attributes = [{
            value : [0.0, 0.0, 0.0],
            componentsPerAttribute : 3
        }];

        var va = new VertexArray({
            context : context,
            attributes : attributes
        });

        expect(function() {
            return va.getAttribute();
        }).toThrowDeveloperError();
    });

    it('fails to destroy', function() {
        var attributes = [{
            value : [0.0, 0.0, 0.0],
            componentsPerAttribute : 3
        }];

        var va = new VertexArray({
            context : context,
            attributes : attributes
        });

        va.destroy();

        expect(function() {
            va.destroy();
        }).toThrowDeveloperError();
    });

    it('throws when there is no context', function() {
        expect(function() {
            return new VertexArray();
        }).toThrowDeveloperError();
    });

    it('throws if instanceDivisor is less than zero', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            vertexBuffer : buffer,
            componentsPerAttribute : 3,
            instanceDivisor : -1
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    // Direct3D 9 requires vertex attribute zero to not be instanced. While ANGLE can work around this, it is best
    // to follow this convention. This test also guarantees that not all vertex attributes are instanced.
    it('throws if vertex attribute zero is instanced', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            index : 0,
            vertexBuffer : buffer,
            componentsPerAttribute : 3,
            instanceDivisor : 1
        }, {
            index : 1,
            vertexBuffer : buffer,
            componentsPerAttribute : 3
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('throws if an attribute has an instanceDivisor and is not backed by a buffer', function() {
        var buffer = Buffer.createVertexBuffer({
            context : context,
            sizeInBytes : 3,
            usage : BufferUsage.STATIC_DRAW
        });

        var attributes = [{
            index : 0,
            vertexBuffer : buffer,
            componentsPerAttribute : 3
        }, {
            index : 1,
            value : [0.0, 0.0, 1.0],
            componentsPerAttribute : 3,
            instanceDivisor : 1
        }];

        expect(function() {
            return new VertexArray({
                context : context,
                attributes : attributes
            });
        }).toThrowDeveloperError();
    });

    it('throws when instanceDivisor is greater than zero and the instanced arrays extension is not supported.', function() {
        if (!context.instancedArrays) {
            var buffer = Buffer.createVertexBuffer({
                context : context,
                sizeInBytes : 3,
                usage : BufferUsage.STATIC_DRAW
            });

            var attributes = [{
                index : 0,
                vertexBuffer : buffer,
                componentsPerAttribute : 3
            }, {
                index : 1,
                vertexBuffer : buffer,
                componentsPerAttribute : 3,
                instanceDivisor : 1
            }];

            expect(function() {
                return new VertexArray({
                    context : context,
                    attributes : attributes
                });
            }).toThrowDeveloperError();
        }
    });
}, 'WebGL');
